package zdream.pmfield.test.data.foe;

import org.junit.Before;
import org.junit.Test;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;

import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import zdream.pmfield.data.foe.*;
import zdream.pmfield.storage.FoeTemplateStorage;
import zdream.pmfield.storage.JsonLoader;
import zdream.pmfield.storage.MovesJsonLoader;
import zdream.pmfield.test.utils.ResourceConstant;

import static org.junit.Assert.*;
import static zdream.pmfield.storage.FoeTemplateStorage.RES_PATH_MOVES_JSON;
import static zdream.pmfield.storage.FoeTemplateStorage.RES_PATH_STAGE_MOVES_JSON;

/**
 * @author Zdream
 * @date 2023-11-10
 * @since 0.0.1
 */
public class MoveCase {
	FoeTemplateStorage storage;

	@Before
	public void before() {
		storage = new FoeTemplateStorage();
		storage.loadMoves();
		storage.loadMovesInResource(ResourceConstant.RES_PATH_TEST_MOVES_JSON);
	}

	@Test
	public void testLoadMove() {
		MoveTemplate move = storage.findMoveById(1, 1);

		assertEquals(move.types.length, 1);
		assertEquals(move.types[0].weight, Type.TypeWithWeight.WEIGHT_FULL);
		assertEquals(move.types[0].type, Type.NORMAL);

		assertEquals(move.attackerUsing, MoveType.PHYSICAL);
		assertEquals(move.defenseUsing, MoveType.PHYSICAL);

		assertEquals(move.vStats.length, 3);
		assertEquals(move.vStats[0], VStat.STRENGTH);
		assertEquals(move.vStats[1], VStat.STRENGTH);
		assertEquals(move.vStats[2], VStat.STRENGTH);

		assertNotNull(move.pairs);
		assertTrue(move.pairs.length > move.vStats.length);

		List<MoveCheckPair> pairs = Arrays.stream(move.pairs)
				.filter(p -> p.level == MoveCheckPair.LEVEL_MORE)
				.collect(Collectors.toList());
		assertTrue(pairs.size() == 1);
	}

	@Test
	public void testCheckMovesJson() {
		Arrays.asList(RES_PATH_MOVES_JSON, RES_PATH_STAGE_MOVES_JSON, ResourceConstant.RES_PATH_TEST_MOVES_JSON)
				.forEach(this::testCheckMovesJsonForPath);
	}

	private void testCheckMovesJsonForPath(String resourcePath) {
		ClassLoader loader = getClass().getClassLoader();
		InputStream inputStream = loader.getResourceAsStream(resourcePath);
		final JsonElement element = JsonLoader.fromInputStream(inputStream);
		assertTrue(element.isJsonArray());

		JsonArray array = element.getAsJsonArray();
		Set<Pair> idSet = new HashSet<>();
		Map<Integer, String> idNameMap = new HashMap<>();

		for (JsonElement jsonItem : array) {
			assertTrue(jsonItem.isJsonObject());

			JsonObject object = jsonItem.getAsJsonObject();
			assertTrue(object.has("id"));

			JsonElement idElement = object.get("id");
			assertTrue(idElement.isJsonPrimitive());
			JsonPrimitive idPrimitive = idElement.getAsJsonPrimitive();
			assertTrue(idPrimitive.isNumber());

			int id = idPrimitive.getAsInt();
			assertTrue(id > 0);
			System.out.println("现在正在检查 " + resourcePath + " 中的元素: id=" + id);

			final MoveTemplate move = MovesJsonLoader.loadOne(object);
			assertNotNull(move);
			assertEquals(id, move.id);

			Pair pair = new Pair(id, move.level);
			assertFalse(idSet.contains(pair));
			idSet.add(pair);

			// check name
			// 每个 id 相同的 move 名称必须一样
			assertNotNull(move.name);
			assertTrue(move.name.length() > 0);
			if (idNameMap.containsKey(move.id)) {
				assertEquals(idNameMap.get(move.id), move.name);
			} else {
				idNameMap.put(move.id, move.name);
			}

			// check level
			assertTrue(move.level > 0);
			assertTrue(move.level < 10);

			// check type
			assertNotNull(move.types);
			int sum = 0;
			for (Type.TypeWithWeight type : move.types) {
				assertNotNull(type);
				sum += type.weight;
			}
			assertEquals(sum, Type.TypeWithWeight.WEIGHT_FULL);
			// 除了 Other 以外, 其余的 type 不能重复
			List<Type> types = Arrays.stream(move.types)
					.map(t -> t.type)
					.filter(t -> t != Type.OTHER)
					.collect(Collectors.toList());
			assertEquals(types.size(), new HashSet<>(types).size());

			// check using
			assertNotNull(move.attackerUsing);
			assertNotNull(move.defenseUsing);
			if (move.attackerUsing == MoveType.STATUS) {
				assertEquals(move.defenseUsing, MoveType.STATUS);
			} else if (move.attackerUsing == MoveType.MOVEMENT) {
				assertEquals(move.defenseUsing, MoveType.MOVEMENT);
			}

			// check vStats
			assertNotNull(move.vStats);
			for (VStat vStat : move.vStats) {
				assertNotNull(vStat);
			}

			// check checkRateFixed
			checkRateFixed(move, object);

			// check pairs
			checkPairs(move, object);

			// check action point / move point
			if (move.actionPoint != MoveTemplate.COST_ALL) {
				assertTrue(move.actionPoint >= 0);
			}
			if (move.movePoint != MoveTemplate.COST_ALL) {
				assertTrue(move.movePoint >= 0);
			}

			// check addition
			if (object.has("a")) {
				assertNotNull(move.additions);
				assertTrue(move.additions.length > 0);

				final JsonElement additionalElement = object.get("a");
				if (additionalElement.isJsonArray()) {
					final JsonArray additionalArray = additionalElement.getAsJsonArray();
					assertEquals(additionalArray.size(), move.additions.length);
				} else {
					assertTrue(additionalElement.isJsonPrimitive());
					assertTrue(additionalElement.getAsJsonPrimitive().isString());
					assertEquals(1, move.additions.length);
				}

				assertTrue(Arrays.stream(move.additions).allMatch(text -> text.length() > 0));
			} else {
				assertNull(move.additions);
			}

			boolean charging = isChargingMove(move);
			if (charging) {
				checkChargingSubMoves(move, object);
			}
		}
	}

	private boolean isChargingMove(MoveTemplate move) {
		return move.hasTags(MoveTemplate.SUB_TITLE_CHARGING);
	}

	public static class Pair {
		public int id;
		public int level;

		public Pair(int id, int level) {
			this.id = id;
			this.level = level;
		}

		@Override
		public boolean equals(Object o) {
			if (this == o) return true;
			if (o == null || getClass() != o.getClass()) return false;
			Pair pair = (Pair) o;
			return id == pair.id && level == pair.level;
		}

		@Override
		public int hashCode() {
			return Objects.hash(id, level);
		}
	}

	private void checkRateFixed(MoveTemplate move, JsonObject object) {
		boolean charging = isChargingMove(move);
		if (charging) {
			assertFalse(object.has("rateFix"));

			MoveTemplate.SubMove subMove = move.findSubMove(MoveTemplate.SUB_TITLE_CHARGING);
			assertEquals(subMove.checkRateFixed, move.checkRateFixed);
			return;
		}

		JsonElement rateElement = object.get("rateFix");
		if (rateElement == null) {
			assertEquals(move.checkRateFixed, 0);
		} else {
			assertTrue(rateElement.isJsonPrimitive());
			assertTrue(rateElement.getAsJsonPrimitive().isNumber());
			assertEquals(move.checkRateFixed, rateElement.getAsInt());
		}
	}

	private void checkPairs(MoveTemplate move, JsonObject object) {
		boolean charging = isChargingMove(move);
		if (charging) {
			assertFalse(object.has("pairs"));
			assertNotNull(move.pairs);
			assertNotNull(move.vStats);
			return;
		}

		assertNotNull(move.pairs);
		assertTrue(move.pairs.length > move.vStats.length);
		JsonElement pairElement = object.get("pairs");
		assertTrue(pairElement.isJsonArray());
		JsonArray pairArray = pairElement.getAsJsonArray();
		assertEquals(pairArray.size(), move.pairs.length);
		assertTrue(pairArray.asList().stream().allMatch(JsonElement::isJsonObject));

		Set<Integer> levels = new HashSet<>();
		for (JsonElement pairItemElement : pairArray.asList()) {
			final JsonObject pairItemObject = pairItemElement.getAsJsonObject();
			assertTrue(pairItemObject.has("lv"));
			final JsonElement pairItemLevelElement = pairItemObject.get("lv");
			assertTrue(pairItemLevelElement.isJsonPrimitive());
			final JsonPrimitive pairItemLevelPrimitive = pairItemLevelElement.getAsJsonPrimitive();

			MoveCheckPair pair0;
			if (pairItemLevelPrimitive.isNumber()) {
				int level = pairItemLevelPrimitive.getAsInt();
				System.out.println("检查 pair:level=" + level);
				assertFalse(levels.contains(level));
				levels.add(level);

				List<MoveCheckPair> pairs = Arrays.stream(move.pairs)
						.filter(p -> p.level == level)
						.collect(Collectors.toList());
				assertEquals(pairs.size(), 1);
				pair0 = pairs.get(0);

				if (level == 0 && move.vStats.length != 0) {
					assertEquals(pair0.result, MoveCheckPair.RESULT_MAJOR_FAILURE);
				} else if (level == move.vStats.length) {
					assertEquals(pair0.result, MoveCheckPair.RESULT_MAJOR_SUCCESS);
					if (move.attackerUsing != MoveType.STATUS && move.attackerUsing != MoveType.MOVEMENT) {
						assertTrue(pair0.power > 0);
					}
				}

				if (move.attackerUsing == MoveType.STATUS || move.attackerUsing == MoveType.MOVEMENT) {
					assertEquals(pair0.power, 0);
				} else {
					if (pair0.result == MoveCheckPair.RESULT_SUCCESS) {
						assertTrue(pair0.power > 0);
					} else if (pair0.result == MoveCheckPair.RESULT_FAILURE) {
						assertEquals(pair0.power, 0);
					}
				}

			} else { // pair named "more"
				assertTrue(pairItemLevelPrimitive.isString());
				System.out.println("检查 pair:level=\"+\"");
				assertFalse(levels.contains((int) MoveCheckPair.LEVEL_MORE));
				levels.add((int) MoveCheckPair.LEVEL_MORE);

				List<MoveCheckPair> pairs = Arrays.stream(move.pairs)
						.filter(p -> p.level == MoveCheckPair.LEVEL_MORE)
						.collect(Collectors.toList());
				assertEquals(pairs.size(), 1);
				pair0 = pairs.get(0);
				assertEquals(pairs.get(0).result, MoveCheckPair.RESULT_MAJOR_SUCCESS);
			}

			// check pairs[x].a
			if (pairItemObject.has("a")) {
				assertNotNull(pair0.additions);
				assertTrue(pair0.additions.length > 0);

				final JsonElement pairItemAdditionalElement = pairItemObject.get("a");
				if (pairItemAdditionalElement.isJsonArray()) {
					final JsonArray pairItemAdditionalArray = pairItemAdditionalElement.getAsJsonArray();
					assertEquals(pairItemAdditionalArray.size(), pair0.additions.length);
				} else {
					assertTrue(pairItemAdditionalElement.isJsonPrimitive());
					assertTrue(pairItemAdditionalElement.getAsJsonPrimitive().isString());
					assertEquals(1, pair0.additions.length);
				}

				assertTrue(Arrays.stream(pair0.additions).allMatch(text -> text.length() > 0));
			} else {
				assertNull(pair0.additions);
			}
		}

		if (levels.contains((int) MoveCheckPair.LEVEL_MORE)) {
			assertEquals(levels.size(), move.vStats.length + 2);
		} else {
			assertEquals(levels.size(), move.vStats.length + 1);
		}

		for (int i = 0; i <= move.vStats.length; i++) {
			assertTrue(levels.contains(i));
		}
	}

	private void checkChargingSubMoves(MoveTemplate move, JsonObject object) {
		assertTrue(object.has(MoveTemplate.SUB_TITLE_CHARGING));
		assertTrue(object.has(MoveTemplate.SUB_TITLE_RELEASE));
		assertEquals(2, move.subs.length);

		// parent
		// Note: all charging move cost all action points and move points
		assertEquals("蓄力攻击 action points 的消费应该是全部", MoveTemplate.COST_ALL, move.actionPoint);
		assertEquals("蓄力攻击 move points 的消费应该是全部", MoveTemplate.COST_ALL, move.movePoint);

		// charging
		MoveTemplate.SubMove subMove = move.findSubMove(MoveTemplate.SUB_TITLE_CHARGING);
		assertNotNull(subMove.vStats);
		assertNotNull(subMove.pairs);

		int length = subMove.vStats.length;
		Map<Integer, MoveCheckPair> pairMap = new HashMap<>();
		Arrays.stream(subMove.pairs).forEach(p -> pairMap.put((int) p.level, p));
		assertEquals(pairMap.size(), subMove.pairs.length);

		for (int i = 0; i <= length; i++) {
			assertNotNull(pairMap.get(i));
		}

		// release
		subMove = move.findSubMove(MoveTemplate.SUB_TITLE_RELEASE);
		assertNotNull(subMove.vStats);
		assertNotNull(subMove.pairs);

		length = subMove.vStats.length;
		pairMap.clear();
		Arrays.stream(subMove.pairs).forEach(p -> pairMap.put((int) p.level, p));
		assertEquals(pairMap.size(), subMove.pairs.length);

		for (int i = 0; i <= length; i++) {
			assertNotNull(pairMap.get(i));
		}

		// only equals to subMove: release
		assertArrayEquals(subMove.vStats, move.vStats);
		assertArrayEquals(subMove.pairs, move.pairs);
		assertEquals(subMove.checkRateFixed, move.checkRateFixed);
	}
}
